package aws

import (
	"os"
	"io"
	"fmt"
	"http"
	"xml"
	"bytes"
	"regexp"
	"strings"
	"hash"
	"crypto/md5"
	"crypto/hmac"
	"encoding/hex"
	"encoding/base64"
)

const (
	REGION_US_EAST_1      = "us-east-1"      // Northern Virginia
	REGION_US_WEST_1      = "us-west-1"      // Northern California
	REGION_EU_WEST_1      = "eu-west-1"      // Ireland
	REGION_AP_SOUTHEAST_1 = "ap-southeast-1" // Singapore
	REGION_AP_NORTHEAST_1 = "ap-northeast-1" // Tokyo
)

var Regions = map[string]string{
	REGION_US_EAST_1:      REGION_US_EAST_1,
	REGION_US_WEST_1:      REGION_US_WEST_1,
	REGION_EU_WEST_1:      REGION_EU_WEST_1,
	REGION_AP_SOUTHEAST_1: REGION_AP_SOUTHEAST_1,
	REGION_AP_NORTHEAST_1: REGION_AP_NORTHEAST_1,
}

func GetEnvRegion() string {
	return Regions[os.Getenv("AWS_REGION")]
}

var encodedChar *regexp.Regexp = regexp.MustCompile("%[0-9][a-z]")

func calculateSignature(h hash.Hash, stringToSign string) string {
	h.Write([]byte(stringToSign))
	buf := make([]byte, base64.StdEncoding.EncodedLen(h.Size()))
	base64.StdEncoding.Encode(buf, h.Sum())
	return string(buf)

}

func CalculateSHA1Signature(secretKey, stringToSign string) string {
	return calculateSignature(hmac.NewSHA1([]byte(secretKey)), stringToSign)
}

func CalculateSHA256Signature(secretKey, stringToSign string) string {
	return calculateSignature(hmac.NewSHA256([]byte(secretKey)), stringToSign)
}

// URLEncode encodes URL components per AWS requirements.
func URLEncode(s string) string {
	r := strings.Replace(http.URLEscape(s), "+", "%20", -1)
	return encodedChar.ReplaceAllStringFunc(r, func(m string) string { return strings.ToUpper(m) })
}

func EndpointURL(endpoint string, secure bool) string {
	if secure {
		return "https://" + endpoint
	}
	return "http://" + endpoint
}

func ReaderMD5Sum(r io.Reader) (sum []byte, err os.Error) {
	h := md5.New()
	_, err = io.Copy(h, r)
	if err != nil {
		return
	}
	return h.Sum(), nil
}

func ReaderContentMD5(r io.Reader) (contentMD5 string, err os.Error) {
	sum, err := ReaderMD5Sum(r)
	if err != nil {
		return
	}
	buf := make([]byte, base64.StdEncoding.EncodedLen(md5.Size))
	base64.StdEncoding.Encode(buf, sum)
	return string(buf), nil
}

func BytesContentMD5(buf []byte) string {
	contentMD5, _ := ReaderContentMD5(bytes.NewBuffer(buf))
	return contentMD5
}

func StringContentMD5(s string) string {
	contentMD5, _ := ReaderContentMD5(strings.NewReader(s))
	return contentMD5
}

func CheckMD5Digest(s, digest string) bool {
	sum, _ := ReaderMD5Sum(strings.NewReader(s))
	return hex.EncodeToString(sum) == strings.ToLower(digest)
}

func GetFirstXMLElementName(r io.Reader) (*xml.Name, os.Error) {
	p := xml.NewParser(r)
	for {
		tok, err := p.Token()
		if err != nil {
			return nil, err
		}

		if t, ok := tok.(xml.StartElement); ok {
			return &t.Name, nil
		}
	}

	return nil, nil // Shouldn't get here
}

func AddAttributeNamesToParams(params map[string]string, names []string) map[string]string {
	if params == nil {
		params = make(map[string]string)
	}

	if len(names) == 1 {
		params["AttributeName"] = names[0]
	} else if len(names) > 1 {
		for i, name := range names {
			params[fmt.Sprintf("AttributeName.%d", i+1)] = name
		}
	}

	return params
}

func AddStringListToParams(params map[string]string, prefix string, sl []string) map[string]string {
	if params == nil {
		params = make(map[string]string)
	}

	for i, s := range sl {
		params[fmt.Sprintf("%s.member.%d", prefix, i+1)] = s
	}

	return params
}
